﻿using System;
using System.Collections.Generic;
using FluentAssertions;
using NUnit.Framework;
using ReactiveGraph.Core;
using ReactiveGraph.Extensions.Rules.Event;
using ReactiveGraph.Object;

namespace ReactiveGraph.Extensions.Rules.Tests
{
    [TestFixture]
    public class RuleTests
    {
        private DefaultNodeContainer _container;

        [SetUp]
        public void Setup()
        {
            _container = new DefaultNodeContainer();
            _container.LoadExtension(new NodeRules());
            _container.LoadExtension(new ReactiveObjectExtension());
            _container.RegisterType<Foo>();
            _container.RegisterType<Bar>();
        }

        [Test]
        public void CanRegisterRule()
        {
            var node = _container.GetNode<IReactiveNode>("Node");
            var p1 = new Property<int>("Prop1");
            node.AttachProperty(p1);
            var p2 = new Property<string>("Prop2");
            node.AttachProperty(p2);

            var rule = Rule.Declare()
                .PropertyChanged(p1)
                .SetProperty(p2, "joe");
            node.Behaviours.Subscribe();

            node.AttachRule(rule);
            p1.Value = 42;
            p2.Value.Should().Be("joe");
        }


        [Test]
        public void RulesDontConflict()
        {
            var parent = _container.GetNode<IReactiveNode>("Node");
            var prop = new Property<string>("foo");
            parent.AttachProperty(prop);
            var actionDone = new List<object>();
            var r = Rule.Declare()
                .PropertyChanged(prop)
                .Action(() => actionDone.Add(new object()));
            parent.AttachRule(r);

            parent.Behaviours.Subscribe();
            var n1 = _container.GetNode<ReactiveNode>("Child");
            parent.ChildNodes.Add(n1);

            actionDone.Should().HaveCount(0);
        }

        [Test]
        public void CanDeclareRuleOnDescendantAdded()
        {
            var parent = _container.GetNode<IReactiveNode>("Node");
            var child = _container.GetNode<ReactiveNode>("child");
            parent.ChildNodes.Add(child);

            var added = new List<IReactiveNode>();
            var r = Rule.Declare()
                .DescendantAdded()
                .With(added.Add);
            parent.AttachRule(r);

            parent.Behaviours.Subscribe();
            var grandChild = _container.GetNode<IReactiveNode>("GrandChild");
            child.ChildNodes.Add(grandChild);

            added.Should().BeEquivalentTo(new[] { grandChild });
        }

        [Test]
        public void CanDeclareRuleOnDescendantRemoved()
        {
            var parent = _container.GetNode<IReactiveNode>("Parent");
            var child = _container.GetNode<ReactiveNode>("child");
            parent.ChildNodes.Add(child);

            var added = new List<IReactiveNode>();
            var r = Rule.Declare()
                .DescendantRemoved()
                .With(added.Add);
            parent.AttachRule(r);

            parent.Behaviours.Subscribe();
            var grandChild = _container.GetNode<IReactiveNode>("GrandChild");
            child.ChildNodes.Add(grandChild);
            child.ChildNodes.Remove(grandChild);

            added.Should().BeEquivalentTo(new[] { grandChild });
        }


        [Test]
        public void ChildNodeAdded()
        {
            var parent = _container.GetNode<IReactiveNode>("Node");
            var added = new List<IReactiveNode>();
            var r = Rule.Declare()
                .ChildAdded(parent)
                .With(added.Add);
            parent.AttachRule(r);

            parent.Behaviours.Subscribe();
            var n1 = _container.GetNode<IReactiveNode>("Child1");
            parent.ChildNodes.Add(n1);

            added.Should().BeEquivalentTo(new[] { n1 });
        }

        [Test]
        public void ChildNodeRemoved()
        {
            var parent = _container.GetNode<IReactiveNode>("Node");
            var added = new List<IReactiveNode>();
            var r = Rule.Declare()
                .ChildRemoved(parent)
                .With(added.Add);
            parent.AttachRule(r);

            parent.Behaviours.Subscribe();
            var n1 = _container.GetNode<IReactiveNode>("Child1");
            parent.ChildNodes.Add(n1);
            parent.ChildNodes.Remove(n1);

            added.Should().BeEquivalentTo(new[] { n1 });
        }

        [Test]
        public void NodeCollectionSupportsChildAdded()
        {
            
            var collection = _container.Get<ReactiveObjectCollection<Foo>>(); ;
            collection.Behaviours.Subscribe();
            var triggered = false;
            var r = Rule.Declare()
                .ChildAdded(collection)
                .With(n => triggered = true);
            collection.AttachRule(r);

            var node = _container.Get<Foo>();
            collection.Add(node);
            triggered.Should().BeTrue();
        }


        class Foo : ReactiveObject<Foo>
        {
            public Foo(Context context)
                : base(context)
            {
            }

            public Bar Bar { get; set; }
            public new Property<string> Name { get; set; }
        }

        class Bar : ReactiveObject<Bar>
        {
            public Bar(Context context)
                : base(context)
            {
            }

            public new Property<string> Name { get; set; }
        }

    }
}
